<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Joshua Hull</title>
    <link>https://joshua_hull.gitlab.io/</link>
      <atom:link href="https://joshua_hull.gitlab.io/index.xml" rel="self" type="application/rss+xml" />
    <description>Joshua Hull</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><copyright>© 2021 Joshua Hull - Licensed under Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.</copyright><lastBuildDate>Sun, 15 Mar 2020 21:00:00 +0000</lastBuildDate>
    <image>
      <url>https://joshua_hull.gitlab.io/images/icon_hu0b7a4cb9992c9ac0e91bd28ffd38dd00_9727_512x512_fill_lanczos_center_2.png</url>
      <title>Joshua Hull</title>
      <link>https://joshua_hull.gitlab.io/</link>
    </image>
    
    <item>
      <title>Building Hours</title>
      <link>https://joshua_hull.gitlab.io/info/pilots-license/building-hours/</link>
      <pubDate>Sun, 15 Mar 2020 21:00:00 +0000</pubDate>
      <guid>https://joshua_hull.gitlab.io/info/pilots-license/building-hours/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;http://www.angelflightsoars.org/pilot-information/pilot-information-pilot-faqs&#34;&gt;Angel Flight Soars&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.gocivilairpatrol.com/media/cms/Pilot_brochure_Final_8312011_12821F1F5CFD0.pdf&#34;&gt;Civil Air Patrol&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Pilot&#39;s License</title>
      <link>https://joshua_hull.gitlab.io/ideas/pilots-license/</link>
      <pubDate>Sun, 15 Mar 2020 20:00:00 +0000</pubDate>
      <guid>https://joshua_hull.gitlab.io/ideas/pilots-license/</guid>
      <description>&lt;h1 id=&#34;private-pilots-license&#34;&gt;Private Pilot&#39;s License&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://www.kingschools.com/cfi/documents/king-schools-private-pilot-syllabus.pdf&#34;&gt;King&#39;s School Private Pilot Syllabus&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://clemsonflyingclub.wixsite.com/clemsonflyingclub&#34;&gt;Clemson University Flying Club&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Checkpoint&lt;/th&gt;
&lt;th&gt;Hours&lt;/th&gt;
&lt;th align=&#34;right&#34;&gt;Approx.&lt;br/&gt;Cost&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Familiarization and&lt;br/&gt;Basic Control&lt;/td&gt;
&lt;td&gt;6.5&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;$975&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refining Control and&lt;br/&gt;Learning to Land&lt;/td&gt;
&lt;td&gt;6.7&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;$990&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expanding Maneuvers and&lt;br/&gt;Landings Skills&lt;/td&gt;
&lt;td&gt;5.9&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;$730&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Night and Cross Country&lt;/td&gt;
&lt;td&gt;14.7&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;$1940&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Earning your certificate&lt;/td&gt;
&lt;td&gt;6.4&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;$880&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;40.2&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;$5515&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id=&#34;instrument-rating&#34;&gt;Instrument Rating&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://www.kingschools.com/cfi/documents/Instrument%20Syllabus%20170727.pdf&#34;&gt;King&#39;s School Instrument Rating Syllabus&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Checkpoint&lt;/th&gt;
&lt;th&gt;Hours&lt;/th&gt;
&lt;th align=&#34;right&#34;&gt;Approx.&lt;br/&gt;Cost&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Learning and Refining Aircraft Control&lt;br/&gt;Using the Instruments&lt;/td&gt;
&lt;td&gt;8.8&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;$1400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Navigating While Flying on Instruments&lt;/td&gt;
&lt;td&gt;9.3&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;$1500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finding the Airport – Flying Instrument Approaches&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;$1800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instrument Cross Countries&lt;/td&gt;
&lt;td&gt;6.7&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;$1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Becoming Instrument Rated&lt;/td&gt;
&lt;td&gt;4.2&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;$670&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;$6400&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Instruction @ $50/hour, Aircraft @ $100/hour &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Instruction @ $50/hour, Aircraft @ $110/hour &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>re:Invent 2018 Reactions</title>
      <link>https://joshua_hull.gitlab.io/post/2018-11-28-reinvent-reactions/</link>
      <pubDate>Wed, 28 Nov 2018 10:49:17 -0500</pubDate>
      <guid>https://joshua_hull.gitlab.io/post/2018-11-28-reinvent-reactions/</guid>
      <description>&lt;p&gt;I&#39;ve been looking on a good topic to post about before I can get into more example posts and with re:Invent going on this week I thought it might be worthwhile talking about a few of the announcements I&#39;m excited about.&lt;/p&gt;
&lt;h3 id=&#34;aws-transfer-for-sftphttpsawsamazoncomblogsawsnew-aws-transfer-for-sftp-fully-managed-sftp-service-for-amazon-s3&#34;&gt;&lt;a href=&#34;https://aws.amazon.com/blogs/aws/new-aws-transfer-for-sftp-fully-managed-sftp-service-for-amazon-s3/&#34;&gt;AWS Transfer for SFTP&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A lot of the data that I handle at work is given to us by vendors/partners as well as clients. While I would love to have them be able to drop the data into S3 a lot of tools and processes revolve around older protocols like SFTP. With AWS offering a managed SFTP server this will be a good bridge between the old world and the new one. I particularly like that the service integrates with external authentication like LDAP and Active Directory. This will allow not only other parties to handle provisioning the accounts but password reset build around those authentication resources.&lt;/p&gt;
&lt;h3 id=&#34;ec2-instances-a1-powered-by-arm-based-aws-graviton-processorshttpsawsamazoncomblogsawsnew-ec2-instances-a1-powered-by-arm-based-aws-graviton-processors&#34;&gt;&lt;a href=&#34;https://aws.amazon.com/blogs/aws/new-ec2-instances-a1-powered-by-arm-based-aws-graviton-processors/&#34;&gt;EC2 Instances (A1) Powered by Arm-Based AWS Graviton Processors&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Back when I first started college I began my academic career studying Electrical Engineering. Ever since then I&#39;ve enjoyed tinkering with electronics whenever I can. Part of the joy of tinkering has meant a fondness for ARM processors. To me, they always seemed so approachable as someone who&#39;s dreamt of building their own computer for so many years. When I heard that AWS had announced the a1 instance family I was very excited. Not only because of that fondness for them but for more practical reasons as well. Things like ARM docker images being built by GitLab, integration testing on Android-based instances, and others make me excited for the future of this and other ARM instance families.&lt;/p&gt;
&lt;p&gt;As a side note, I was able to spin up a Docker Swarm cluster on a1 instanced within 24 hours of their being announced. I&#39;m hoping to have a blog post about that ready shortly that also discusses some other ideas and a few I&#39;ve mentioned here more in depth.&lt;/p&gt;
&lt;h3 id=&#34;firecracker--lightweight-virtualization-for-serverless-computing&#34;&gt;Firecracker – Lightweight Virtualization for Serverless Computing&lt;/h3&gt;
&lt;p&gt;This particular announcement I&#39;m most excited about because I&#39;m excited to see what the community with do with it. I can envision local versions of Lambda being developed as well as the project being integrated into other projects or services.&lt;/p&gt;
&lt;h3 id=&#34;aws-ground-stationhttpsawsamazoncomblogsawsaws-ground-station-ingest-and-process-data-from-orbiting-satellites&#34;&gt;&lt;a href=&#34;https://aws.amazon.com/blogs/aws/aws-ground-station-ingest-and-process-data-from-orbiting-satellites/&#34;&gt;AWS Ground Station&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Anyone who follows me on twitter (you do follow me, &lt;a href=&#34;https://twitter.com/joshua_hull&#34;&gt;don&#39;t you?&lt;/a&gt;) knows that I&#39;m a big fan of everything New Space/Space 2.0/what ever you want to call it. If anyone is looking for someone to pay to build and launch a CubeSat feel free to drop me a line. Anyway, this announcement to me means that a big player in the cloud space is ready to take New Space seriously. I&#39;ve seen several people wonder why this service would be offered. To me, it doesn&#39;t seem all that surprising. Jeff Bezos is personally funding a rocket company. In addition, I know of one other company that is leveraging AWS and had modeled their process in a similar way. My only hope is that AWS takes &amp;ldquo;cloud&amp;rdquo; computing literally and we see a new region announced: leo-1&lt;/p&gt;
&lt;h3 id=&#34;amazon-fsx-for-windows-file-serverhttpsawsamazoncomblogsawsnew-amazon-fsx-for-windows-file-server-fast-fully-managed-and-secure&#34;&gt;&lt;a href=&#34;https://aws.amazon.com/blogs/aws/new-amazon-fsx-for-windows-file-server-fast-fully-managed-and-secure/&#34;&gt;Amazon FSx for Windows File Server&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Like I mentioned in the section about &lt;a href=&#34;#aws-transfer-for-sftp&#34;&gt;AWS Transfer for SFTP&lt;/a&gt; a lot of the workloads I see on a day to day basis are based off older technologies. I&#39;ve also seen many based on Windows technologies (I&#39;m more of a Linux fan/user). Having a Windows-based file share means being able to more easily interface with those processes that aren&#39;t able to adapt to new AWS-centric technologies like S3.&lt;/p&gt;
&lt;h3 id=&#34;control-tower&#34;&gt;Control Tower&lt;/h3&gt;
&lt;p&gt;As someone who&#39;s spent two and a half years working on the AWS platform, I think I can say this with a level of confidence: Best practices are hard. Setting everything up, monitoring, all of the various tasks. The fact that AWS is giving an out of the box way to accomplish this hopefully will lead to more and more of these best practices being adopted and all the benefits associated with them reaped.&lt;/p&gt;
&lt;h3 id=&#34;amazon-textract&#34;&gt;Amazon Textract&lt;/h3&gt;
&lt;p&gt;Recently here at work, there was a discussion about the possibility of using AWS&amp;rsquo; Machine Learning technologies to extract information from forms that would be sent to us by customers. We ended up going in a different direction but the idea of being able to scan documents and extract the results got me thinking. I personally would use it to take care of receipts and things like that. With Textract all of that is an S3 bucket, a Lambda function, and a scanner away. Receipts, forms, bills, a plethora of items could be scanned, organized, and tagged.&lt;/p&gt;
&lt;h3 id=&#34;aws-outposts&#34;&gt;AWS Outposts&lt;/h3&gt;
&lt;p&gt;With all these posts about AWS, it might surprise people to know that I&#39;m also a sucker for physical servers too. There&#39;s just something about having a big, loud, power hungry stack of computers that lets you know work is getting done. I&#39;ve always secretly wanted to run my own personal cloud based either out of my home or a local data center when the power bill gets too high. Projects like &lt;a href=&#34;https://www.openstack.org&#34;&gt;Openstack&lt;/a&gt; have meant that that was a real possibility. With the announcement of Outposts, this dream of running a cloud locally is even more possible! In reality, I can see this being used in a lot of places. Manufacturing, Education, the list goes on. My only concern is how much is it going to cost for me to have us-joshs-cloud be a reality.&lt;/p&gt;
&lt;h3 id=&#34;aws-managed-blockchain&#34;&gt;AWS Managed Blockchain&lt;/h3&gt;
&lt;p&gt;I&#39;ve had an issue in GitLab for the blog for a post about developing a CI/CD pipeline for Ethereum contracts. One of the biggest headaches I&#39;ve been trying to wrap my head around has been how to deploy to the production network and public test networks. Testing is easier thanks to the folks who developed &lt;a href=&#34;https://truffleframework/ganache&#34;&gt;Ganache&lt;/a&gt; but the public networks are a different story. I&#39;ve debated docker containers and AMIs and how to keep the startup time for them down to a minimum and other complications. However, with AWS Managed Blockchain I expect a lot of those headaches to go away. With this service, I&#39;m hoping that AWS removes a lot of the operational headaches of running an Ethereum node and allow for more rapid development on the platform.&lt;/p&gt;
&lt;h3 id=&#34;aws-lake-formation&#34;&gt;AWS Lake Formation&lt;/h3&gt;
&lt;p&gt;Earlier this year we undertook an exploratory dive into migrating our process from its current state to that of a modern data architecture. This involved the setup of a data lake and exploring various ETL tools. While our particular use case we were exploring didn&#39;t quite fit I can say that the concept definitely stuck with me. What also stuck with me was the complexity of such an undertaking. Based on what I saw with Lake Formation I&#39;m eager to see what headway AWS has made into simplifying the process of moving to a data lake strategy.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This is turning out to be the longest post I&#39;ve written for any of the iterations of this blog so I&#39;m going to leave it there for now. I&#39;ll write the rest of my reactions in part two to be released shortly.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>CI/CD With Cloudformation</title>
      <link>https://joshua_hull.gitlab.io/post/2018-09-10-infrastructure-as-code/</link>
      <pubDate>Mon, 10 Sep 2018 11:03:43 -0400</pubDate>
      <guid>https://joshua_hull.gitlab.io/post/2018-09-10-infrastructure-as-code/</guid>
      <description>&lt;p&gt;Some astute reads may have noticed a design change around this blog and you would be correct. I&#39;ve migrated it over to the awesome &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt; engine and the &lt;a href=&#34;https://themes.gohugo.io/academic/&#34;&gt;Academic Theme&lt;/a&gt;. All of this was of course facilitated by the choice to migrate over to &lt;a href=&#34;https://gitlab.com&#34;&gt;GitLab&lt;/a&gt;. There were a couple of reasons that I decided to move over but the major one was being able to have CI/CD directly tied into my repos. I&#39;m a big proponent of CI/CD and DevOps, so much so that it&#39;s one of my roles at my full time jobs in addition to all the AWS work I do. How those two roles intersect brings us to the motivation behind this post. I&#39;ve started using CloudFormation, AWS&amp;rsquo; Infrastructure as Code offering, more and more at work. The ability to reliably reproduce work, change track it, and deploy it multiple times is quite simple awesome. When I decided to make the shift over to GitLab I chose CloudFormation as the first task to use GitLab&#39;s CI/CD features, other then this blog of course. I have to say that is was remarkably straight forward to set up. I&#39;ve created a repo over at &lt;a href=&#34;https://gitlab.com/joshua_hull/cfn-templates&#34;&gt;https://gitlab.com/joshua_hull/cfn-templates&lt;/a&gt; that includes some working examples of templates as well as the .gitlab-ci.yml that I use for the project. As I do more work with CloudFormation I hope to add more the project but for now it serves as a good example for anyone wanting to explore CI/CD and CloudFormation on GitLab.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Using Scheduled Lambda Functions To Snapshot Volumes</title>
      <link>https://joshua_hull.gitlab.io/post/2018-09-10-lambda-snapshot/</link>
      <pubDate>Mon, 10 Sep 2018 10:22:40 -0400</pubDate>
      <guid>https://joshua_hull.gitlab.io/post/2018-09-10-lambda-snapshot/</guid>
      <description>&lt;p&gt;One of the primary methods I use for backups when working inside AWS is the use of snapshots. They&#39;re straight forward in AWS and they&#39;re available in the AWS CLI which means they are scriptable. In the past I&#39;d write a simple BASH script and schedule it with Cron but this is the year of Serverless so I decided to re-write the process with AWS Lambda. While I&#39;m not going to post the exact code I&#39;ll cover a few of the tidbits below.&lt;/p&gt;
&lt;p&gt;The premise of the script is that it will run on some schedule and describe all volumes in the account it&#39;s operating under. It will then filter out volumes that don&#39;t contain at least one tag that matches a regular expression. This RegEx will make sure we&#39;re working on volumes that contain the schedule the volume should have a snapshot taken on, the last time it was run, the name of the snapshot, etc.&lt;/p&gt;
&lt;p&gt;Given my familiarity with the JavaScript AWS SDK and NodeJS in general I chose to use that framework to develop the lambda script. Since the JS SDK can return Promises I tend to make these types of lambdas a chain of promises, similar to this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;
params = { ... }
EC2.describeVolumes(params).promise().then(data =&amp;gt; {
    let promises = []
    // Magic goes here.
    return Promise.all(promises);
})
.then (data =&amp;gt; {
    let promises = []
    // Even more magic goes here.
    return Promise.all(promises);
})
.then(data =&amp;gt; {
    callback(null, data);
})
.catch(err =&amp;gt; {
    callback(err);
})

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fill in the blanks and you have a pretty handy Lambda function that can work with not only the EC2 API but any AWS service that returns promises through the JS SDK. One example is the SNS service. I use this one to send out emails when the snapshot has been preformed but you could just as easily send them out in the &lt;code&gt;catch()&lt;/code&gt; to report errors instead of success.&lt;/p&gt;
&lt;p&gt;One big thing to emphasize though is that most of the configuration happens on the volumes themselves. In my particular use case the volumes have snapshots created varying schedules. This requires setting up multiple cron jobs or lambdas with different configurations. However, with this new lambda each volume can have its own configuration that can be updated with me having to re-deploy the lambda with a new configuration itself.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Lambda Function and Encrypted S3</title>
      <link>https://joshua_hull.gitlab.io/post/2017-05-05-lambda-function-and-encrypted-s3/</link>
      <pubDate>Fri, 05 May 2017 01:09:22 -0400</pubDate>
      <guid>https://joshua_hull.gitlab.io/post/2017-05-05-lambda-function-and-encrypted-s3/</guid>
      <description>&lt;p&gt;One of the aspects of AWS Lambda&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; that makes it excepent is that Lambda is used to extend other services offered by AWS. In this example we will set up Lambda to use Server Side Encryption for any object uploaded to AWS S3&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The first task we have is to write the lambda function. Below we have the Python code that will read in the metadata about the object that was uploaded and copy it to the same path in the same S3 bucket if SSE is not enabled.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;
import boto3
import os
import sys
import uuid

def check_if_unencrypted(bucket, key):
    s3 = boto3.resource(&#39;s3&#39;)
    obj = s3.Object(&#39;bucket_name&#39;,&#39;key&#39;)

    return not obj.server_side_encryption


def handler(event, context):
    s3 = boto3.client(&#39;s3&#39;)

    for record in event[&#39;Records&#39;]:
        bucket = record[&#39;s3&#39;][&#39;bucket&#39;][&#39;name&#39;]
        key = record[&#39;s3&#39;][&#39;object&#39;][&#39;key&#39;]

        if check_if_unencrypted(bucket, key):
            download_path = &#39;/tmp/{}&#39;.format(uuid.uuid4())

            s3.download_file(bucket, key, download_path)
            data = open(&#39;test.jpg&#39;, &#39;rb&#39;)

            s3.put_object(Bucket=bucket, ServerSideEncryption=&#39;AES256&#39;, Body=data)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have out lambda function written we need to create the lambda function inside AWS. The following commands will create the AWS role for Lambda. We first need to create two files. The first is the Trust Policy for the IAM role that will allow Lambda to assume the role.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;
{
  &amp;quot;Version&amp;quot;: &amp;quot;2012-10-17&amp;quot;,
  &amp;quot;Statement&amp;quot;: [
    {
      &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
      &amp;quot;Principal&amp;quot;: {
        &amp;quot;Service&amp;quot;: &amp;quot;lambda.amazonaws.com&amp;quot;
      },
      &amp;quot;Action&amp;quot;: &amp;quot;sts:AssumeRole&amp;quot;
    }
  ]
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second file will be the permissions that go along with the role. Note that these permissions give full access to the bucket. Use with caution.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;
{
  &amp;quot;Version&amp;quot;: &amp;quot;2012-10-17&amp;quot;,
  &amp;quot;Statement&amp;quot;: {
    &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
    &amp;quot;Action&amp;quot;: &amp;quot;s3:*&amp;quot;,
    &amp;quot;Resource&amp;quot;: &amp;quot;arn:aws:s3:::example_bucket&amp;quot;
  },
  {
        &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
        &amp;quot;Action&amp;quot;: [
          &amp;quot;logs:*&amp;quot;
        ],
        &amp;quot;Resource&amp;quot;: &amp;quot;arn:aws:logs:*:*:*&amp;quot;
      }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have those two files, refered from here on as trust.json and permissions.json, we can run the commands to create the role and the lambda function.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;
aws iam create-role --role-name LambdaRole --assume-role-policy-document file://trust.json

aws iam put-role-policy --role-name LambdaRole --policy-name S3FullAccess --policy-document file://permissions.json

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we&#39;ve created the role for Lambda to use we can create the function. We&#39;ll need to ZIP up the code and then upload it for Lambda to run. We will also need to the role ARN from above when we create the function.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;
zip -9 main.zip main.py

aws lambda create-function \
--region us-east-1 \
--function-name EncryptS3 \
--zip-file fileb://main.zip \
--role arn:aws:iam:us-east-1:123456789012:role:LambdaRole \
--handler main.handler \
--runtime python3.6 \
--profile adminuser \
--timeout 10 \
--memory-size 1024

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we need to configure both Lambda and S3 to handle notifying Lambda when an object is places in an S3 bucket. We will need another JSON file, policy.json, with the following content that will allow the Lambda Function to access objects in the S3 bucket.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;
{
   &amp;quot;Statement&amp;quot;: [
      {
         &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
         &amp;quot;Principal&amp;quot;: {
            &amp;quot;AWS&amp;quot;: &amp;quot;arn:aws:lambda:us-east-1:123456789012:function:LambdaRole&amp;quot;
         },
         &amp;quot;Action&amp;quot;: [
            &amp;quot;s3:*&amp;quot;
         ],
         &amp;quot;Resource&amp;quot;: [
            &amp;quot;arn:aws:s3:::example_bucket/*&amp;quot;,
            &amp;quot;arn:aws:s3:::example_bucket&amp;quot;
         ]
      }
   ]
}

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;
aws lambda add-permission \
--function-name EncryptS3 \
--region us-east-1 \
--statement-id some-unique-id \
--action lambda:InvokeFunction \
--principal s3.amazonaws.com \
--source-arn arn:aws:s3:::example_bucket \
--source-account 123456789012 \
--profile adminuser

aws s3api put-bucket-policy --bucket MyBucket --policy file://policy.json

aws iam create-role \
  --role-name InvokeLambdaRole \
  --assume-role-policy-document &#39;{
      &amp;quot;Version&amp;quot;: &amp;quot;2012-10-17&amp;quot;,
      &amp;quot;Statement&amp;quot;: [
        {
          &amp;quot;Sid&amp;quot;: &amp;quot;&amp;quot;,
          &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
          &amp;quot;Principal&amp;quot;: {
            &amp;quot;Service&amp;quot;: &amp;quot;s3.amazonaws.com&amp;quot;
          },
          &amp;quot;Action&amp;quot;: &amp;quot;sts:AssumeRole&amp;quot;,
          &amp;quot;Condition&amp;quot;: {
            &amp;quot;StringLike&amp;quot;: {
              &amp;quot;sts:ExternalId&amp;quot;: &amp;quot;arn:aws:s3:::*&amp;quot;
            }
          }
        }
      ]
    }&#39;

aws iam put-role-policy \
  --role-name InvokeLambdaRole \
  --policy-name InvokeLambdaPolicy \
  --policy-document &#39;{
     &amp;quot;Version&amp;quot;: &amp;quot;2012-10-17&amp;quot;,
     &amp;quot;Statement&amp;quot;: [
       {
         &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
         &amp;quot;Action&amp;quot;: [
           &amp;quot;lambda:InvokeFunction&amp;quot;
         ],
         &amp;quot;Resource&amp;quot;: [
           &amp;quot;*&amp;quot;
         ]
       }
     ]
   }&#39;

aws s3api put-bucket-notification \
  --bucket example_bucket \
  --notification-configuration &#39;{
    &amp;quot;CloudFunctionConfiguration&amp;quot;: {
      &amp;quot;CloudFunction&amp;quot;: &amp;quot;arn:aws:lambda:us-east-1:123456789012:function:LambdaRole&amp;quot;,
      &amp;quot;InvocationRole&amp;quot;: &amp;quot;arn:aws:iam:us-east-1:123456789012:role:InvokeLambdaRole&amp;quot;,
      &amp;quot;Event&amp;quot;: &amp;quot;s3:ObjectCreated:*&amp;quot;
    }
  }&#39;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s everything that&#39;s needed. You should be able to upload an object to the S3 bucket and it will be re-uploaded with Server Side Encryption. Go ahead and give it a try and let me know what you think in the comments below. If you have an questions or issues leave a comment or reach out to me on twitter.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://aws.amazon.com/lambda&#34;&gt;https://aws.amazon.com/lambda&lt;/a&gt; &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://aws.amazon.com/s3&#34;&gt;https://aws.amazon.com/s3&lt;/a&gt; &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Developing Visualization for Security Groups</title>
      <link>https://joshua_hull.gitlab.io/post/2017-03-29-ec2-connected-graph/</link>
      <pubDate>Wed, 29 Mar 2017 13:02:32 -0400</pubDate>
      <guid>https://joshua_hull.gitlab.io/post/2017-03-29-ec2-connected-graph/</guid>
      <description>&lt;p&gt;I was working on a task yesterday and throught I would write it up so that others could possibly benefit from it. I was working to document our AWS enviornment, specifically the security groups around each instance and how the instances are connected to each other and the internet as a whole.&lt;/p&gt;
&lt;p&gt;I had been asked several weeks ago if there was some documentation of the AWS environment at work and how instances were interconnected. I didn&#39;t have any documentation at the time and it wasn&#39;t a huge deal so I let the topic drop. The other day however I was thinking about documenting AWS and that conversion came back into my head. I realized that with the AWS API you could generate the graph of the connections realtivly easily. Since everyone loves pretty pictures I wanted to see if I could visualize it as well.&lt;/p&gt;
&lt;p&gt;The first step was to generate the graph of the security groups. This was another chance to use boto3&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, the de-facto Python binding for the AWS API. After toying around I had the code that could generate the graph between each instance and the IPs that were allowed inbound access.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;
import boto3
import json

def searchIpAddress(ipAddress):
    inputAddress = ipAddress.split(&#39;/&#39;)[0]
    returnString = ipAddress

    ec2 = boto3.resource(&#39;ec2&#39;)
    for instance in ec2.instances.all():
        for interface in instance.network_interfaces_attribute:
             if interface[&#39;PrivateIpAddress&#39;] == inputAddress:
                 returnString = instance.instance_id
             if &#39;Association&#39; in interface:
                if interface[&#39;Association&#39;][&#39;PublicIp&#39;] == inputAddress:
                    returnString = instance.instance_id
    return returnString

ec2 = boto3.resource(&#39;ec2&#39;)

graph = {}
cache = {}

for instance in ec2.instances.all():
    graph[instance.instance_id] = {}
    for interface in instance.network_interfaces_attribute:
        for group in interface[&#39;Groups&#39;]:
            security_group = ec2.SecurityGroup(group[&#39;GroupId&#39;])
            for permission in security_group.ip_permissions:
                for IP in permission[&#39;IpRanges&#39;]:
                    if IP[&#39;CidrIp&#39;] not in cache.keys():
                        cache[IP[&#39;CidrIp&#39;]] = searchIpAddress(IP[&#39;CidrIp&#39;])
                    source = cache[IP[&#39;CidrIp&#39;]]
                    if IP[&#39;CidrIp&#39;] in graph[instance.instance_id].keys():
                        graph[instance.instance_id][source] = graph[instance.instance_id][source] + 1
                    else:
                        graph[instance.instance_id][source] = 1

nodes = []
links = []

for node in graph:
    nodes.append({&#39;id&#39;: node, &#39;group&#39;: 1})
    for edge in graph[node]:
        group = 2
        if edge.split(&#39;-&#39;)[0] == &#39;i&#39;:
            group = 1
        nodes.append({&#39;id&#39;: edge, &#39;group&#39;: group})
        links.append({&amp;quot;source&amp;quot;: edge, &amp;quot;target&amp;quot;: node, &amp;quot;value&amp;quot;: graph[node][edge]})

seen_nodes = set()
nodes_dedup = []
for obj in nodes:
    if obj[&#39;id&#39;] not in seen_nodes:
        nodes_dedup.append(obj)
        seen_nodes.add(obj[&#39;id&#39;])

data = {&#39;nodes&#39;: nodes_dedup, &#39;links&#39;: links}

print(json.dumps(data))

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&#39;s one aspect of the code that I want to specifically call out. The method &lt;code&gt;searchIpAddress&lt;/code&gt; searches for the instance ID for an IP address. This way if a security group refrences another instance the graph can properly know that. If we were to do this blindly though we would be pulling the list of instances from the API for each IP address for each port. Since we don&#39;t want to be wasteful we cache the results and preform a lookup in the cache and only call the method if we&#39;ve not encountered that IP address before.&lt;/p&gt;
&lt;p&gt;Now that we have the graph we need a way to visualize it. Here I wanted to use a Directed Graph&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; from D3&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;, the JavaScript visualization library. The python code above will output the graph in a way that can be used by D3. While I was able to get the graph working correctly I felt like the visualization was lacking. The graph couldn&#39;t handle the fact that the secutiy groups would have multiple ports vert well. It also couldn&#39;t handle the structure of our network very well but that was certainly no fault of the graph.&lt;/p&gt;
&lt;p&gt;Overall I&#39;m happy with the exercise. While the visualization aspect didn&#39;t turn out how I hoped I was able to get the data and have a way to reproduce it in the future should anyone need it. I hope to incorporate RDS as well in the future but that presents a different set of challenges.&lt;/p&gt;
&lt;p&gt;If you&#39;ve got questions about Amazon Web Services&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; or cloud technology in general feel free to contact me and I&#39;ll see how I can help bring my experience to the problem.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://boto3.readthedocs.io/en/latest/&#34;&gt;https://boto3.readthedocs.io/en/latest/&lt;/a&gt; &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://bl.ocks.org/mbostock/4062045&#34;&gt;https://bl.ocks.org/mbostock/4062045&lt;/a&gt; &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://d3js.org/&#34;&gt;https://d3js.org/&lt;/a&gt; &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://aws.amazon.com/&#34;&gt;https://aws.amazon.com/&lt;/a&gt; &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Architecting Serverless Dynamic DNS Using AWS Services</title>
      <link>https://joshua_hull.gitlab.io/post/2016-06-24-serverless-dynamic-dns/</link>
      <pubDate>Fri, 24 Jun 2016 16:52:00 -0400</pubDate>
      <guid>https://joshua_hull.gitlab.io/post/2016-06-24-serverless-dynamic-dns/</guid>
      <description>&lt;p&gt;The inspiration for this post and much of its content comes from &lt;a href=&#34;https://medium.com/aws-activate-startup-blog/building-a-serverless-dynamic-dns-system-with-aws-a32256f0a1d8#.6tzj1o286&#34;&gt;https://medium.com/aws-activate-startup-blog/building-a-serverless-dynamic-dns-system-with-aws-a32256f0a1d8#.6tzj1o286&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You&#39;ve recently set up a server at your home. You don&#39;t quite feel comfortable hosting it in a service like AWS or you happened to have a machine lying around you want to try and get some use out of. You&#39;ve gotten it up and running and forwarded incoming traffic from your router to be forwarded to the server. You set up the DNS and are happy with the results.&lt;/p&gt;
&lt;p&gt;Several weeks go by and you&#39;re at work. The weather is bad and you find out power was interrupted at your home. You are worried about the server (You didn&#39;t use a surge protector or a UPS, did you?) and decide to try and connect. As you fear you can&#39;t you go about your work and head home at the end of the day. The weather is clear and you arrive home to find the power is on. You try to connect to your server and everything is fine. You work into the evening and go to bed.&lt;/p&gt;
&lt;p&gt;The next day at work you are trying to connect to your server again and find you can&#39;t. You try everything but nothing works. You get home and find you can connect fine. You decide to check the external IP address has changed. You call your ISP and find out that they issue dynamic addresses to residential customers and either won&#39;t give you a static one or are going to charge you far too much for one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://joshua_hull.gitlab.io/img/_posts/aws-dynamic-dns.png&#34; alt=&#34;Dynamic DNS in AWS&#34;&gt;&lt;/p&gt;
&lt;p&gt;After some research you develop a plan to use &lt;a href=&#34;%5Bhttps://aws.amazon.com/api-gateway/%5D&#34;&gt;AWS API Gateway&lt;/a&gt; and &lt;a href=&#34;https://aws.amazon.com/lambda&#34;&gt;AWS Lamda&lt;/a&gt;. You plan on having a single API endpoint with two modes, &lt;code&gt;get&lt;/code&gt; and &lt;code&gt;set&lt;/code&gt;. The &lt;code&gt;get&lt;/code&gt; method will simply return the IP address of whoever called the API. An example of calling the endpoint in the &lt;code&gt;get&lt;/code&gt; mode is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;
wget https://....amazonaws.com/prod?mode=get

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The return value for the call will be the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;
{
  “return_message”: “176.32.100.36”,
  “return_status”: “success”
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The client can then use this information to calculate a secure SHA256 hash of the information it needs to pass to the API in the &lt;code&gt;set&lt;/code&gt; mode. This hash will consist of &lt;code&gt;IP_AddressHost_NameShared_Secret&lt;/code&gt;. If the client wants to update the IP address for &lt;code&gt;host1.dyn.example.com&lt;/code&gt; to &lt;code&gt;192.168.0.1&lt;/code&gt; with the shared secret of &lt;code&gt;P@ssw0rd&lt;/code&gt; then it would pass &lt;code&gt;SHA256(192.168.0.1host1.dyn.example.comP@ssw0rd)&lt;/code&gt; in the &lt;code&gt;set&lt;/code&gt; as show below:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;
HASH=`echo -n 192.168.0.1host1.dyn.example.comP@ssw0rd | shasum -a 256`

wget https://....amazonaws.com/prod?mode=set&amp;amp;hostname=host1.dyn.example.com&amp;amp;hash=$HASH

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the hostname does not need to be updated the following will be the return value:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;
{
  “return_message”: “Your IP address matches the current Route53 DNS record.”,
  “return_status”: “success”
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the hostname is updated then the following will be the return value:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;
{
  “return_message”: “Your hostname record host1.dyn.example.com. has been set to 176.32.100.36”,
  “return_status”: “success”
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You setup the API inside of AWS and configure it to use Lamda as the backend. You create a single Lamda function to use and after some trial and error have the following result:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;
from __future__ import print_function

import json
import re
import hashlib
import boto3

config_s3_region = &#39;us-west-2&#39;
config_s3_bucket = &#39;my_bucket_name&#39;
config_s3_key = &#39;config.json&#39;

def read_s3_config():
    s3_client = boto3.client(
        &#39;s3&#39;,
        config_s3_region,
    )

    s3_client.download_file(
        config_s3_bucket,
        config_s3_key,
        &#39;/tmp/%s&#39; % config_s3_key
    )

    full_config = (open(&#39;/tmp/%s&#39; % config_s3_key).read())
    return json.loads(full_config)

def route53_client(
  execution_mode,
  aws_region,
  route_53_zone_id,
  route_53_record_name,
  route_53_record_ttl,
  route_53_record_type,
  public_ip
  ):

    route53_client = boto3.client(
        &#39;route53&#39;,
        region_name=aws_region
    )

    if execution_mode == &#39;get_record&#39;:
        current_route53_record_set = route53_client.list_resource_record_sets(
            HostedZoneId=route_53_zone_id,
            StartRecordName=route_53_record_name,
            StartRecordType=route_53_record_type,
            MaxItems=&#39;2&#39;
        )

        for eachRecord in current_route53_record_set[&#39;ResourceRecordSets&#39;]:
            if eachRecord[&#39;Name&#39;] == route_53_record_name:
                if len(eachRecord[&#39;ResourceRecords&#39;]) == 1:
                    for eachSubRecord in eachRecord[&#39;ResourceRecords&#39;]:
                        currentroute53_ip = eachSubRecord[&#39;Value&#39;]
                        return_status = &#39;success&#39;
                        return_message = currentroute53_ip
                        return {&#39;return_status&#39;: return_status,
                                &#39;return_message&#39;: return_message}
                elif len(eachRecord[&#39;ResourceRecords&#39;]) &amp;gt; 1:
                    return_status = &#39;fail&#39;
                    return_message = &#39;You should only have a single value for&#39;\
                    &#39; your dynamic record.  You currently have more than one.&#39;
                    return {&#39;return_status&#39;: return_status,
                            &#39;return_message&#39;: return_message}

    if execution_mode == &#39;set_record&#39;:
        change_route53_record_set = route53_client.change_resource_record_sets(
            HostedZoneId=route_53_zone_id,
            ChangeBatch={
                &#39;Changes&#39;: [
                    {
                        &#39;Action&#39;: &#39;UPSERT&#39;,
                        &#39;ResourceRecordSet&#39;: {
                            &#39;Name&#39;: route_53_record_name,
                            &#39;Type&#39;: route_53_record_type,
                            &#39;TTL&#39;: route_53_record_ttl,
                            &#39;ResourceRecords&#39;: [
                                {
                                    &#39;Value&#39;: public_ip
                                }
                            ]
                        }
                    }
                ]
            }
        )
        return 1

def run_set_mode(set_hostname, validation_hash, source_ip):
    try:
        full_config = read_s3_config()
    except:
        return_status = &#39;fail&#39;
        return_message = &#39;There was an issue finding &#39;\
            &#39;or reading the S3 config file.&#39;
        return {&#39;return_status&#39;: return_status,
                &#39;return_message&#39;: return_message}

    record_config_set = full_config[set_hostname]
    aws_region = record_config_set[&#39;aws_region&#39;]
    route_53_zone_id = record_config_set[&#39;route_53_zone_id&#39;]
    route_53_record_ttl = record_config_set[&#39;route_53_record_ttl&#39;]
    route_53_record_type = record_config_set[&#39;route_53_record_type&#39;]
    shared_secret = record_config_set[&#39;shared_secret&#39;]

    if not re.match(r&#39;[0-9a-fA-F]{64}&#39;, validation_hash):
        return_status = &#39;fail&#39;
        return_message = &#39;You must pass a valid sha256 hash in the &#39;\
            &#39;hash= argument.&#39;
        return {&#39;return_status&#39;: return_status,
                &#39;return_message&#39;: return_message}


    calculated_hash = hashlib.sha256(source_ip + set_hostname + shared_secret).hexdigest()

    if not calculated_hash == validation_hash:
        return_status = &#39;fail&#39;
        return_message = &#39;Validation hashes do not match.&#39;
        return {&#39;return_status&#39;: return_status,
                &#39;return_message&#39;: return_message}
    else:
        route53_get_response = route53_client(
            &#39;get_record&#39;,
            aws_region,
            route_53_zone_id,
            set_hostname,
            route_53_record_ttl,
            route_53_record_type,
            &#39;&#39;)

        if not route53_get_response:
            route53_ip = &#39;0&#39;
        elif route53_get_response[&#39;return_status&#39;] == &#39;fail&#39;:
            return_status = route53_get_response[&#39;return_status&#39;]
            return_message = route53_get_response[&#39;return_message&#39;]
            return {&#39;return_status&#39;: return_status,
                    &#39;return_message&#39;: return_message}
        else:
            route53_ip = route53_get_response[&#39;return_message&#39;]

        if route53_ip == source_ip:
            return_status = &#39;success&#39;
            return_message = &#39;Your IP address matches &#39;\
                &#39;the current Route53 DNS record.&#39;
            return {&#39;return_status&#39;: return_status,
                    &#39;return_message&#39;: return_message}
        else:
            return_status = route53_client(
                &#39;set_record&#39;,
                aws_region,
                route_53_zone_id,
                set_hostname,
                route_53_record_ttl,
                route_53_record_type,
                source_ip)
            return_status = &#39;success&#39;
            return_message = &#39;Your hostname record &#39; + set_hostname +\
                &#39; has been set to &#39; + source_ip
            return {&#39;return_status&#39;: return_status,
                    &#39;return_message&#39;: return_message}

def lambda_handler(event, context):

    execution_mode = event[&#39;execution_mode&#39;]
    source_ip = event[&#39;source_ip&#39;]
    query_string = event[&#39;query_string&#39;]
    validation_hash = event[&#39;validation_hash&#39;]
    set_hostname = event[&#39;set_hostname&#39;]

    execution_modes = (&#39;set&#39;, &#39;get&#39;)
    if execution_mode not in execution_modes:
        return_status = &#39;fail&#39;
        return_message = &#39;You must pass mode=get or mode=set arguments.&#39;
        return_dict = {&#39;return_status&#39;: return_status,
                       &#39;return_message&#39;: return_message}

    if execution_mode == &#39;get&#39;:
        return_status = &#39;success&#39;
        return_message = source_ip
        return_dict = {&#39;return_status&#39;: return_status,
                       &#39;return_message&#39;: return_message}
    else:
        return_dict = run_set_mode(set_hostname, validation_hash, source_ip)

    return return_dict

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An example of the configuration file stored in S3 is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;
{
    &amp;quot;host1.dyn.example.com.&amp;quot;: {
        &amp;quot;aws_region&amp;quot;: &amp;quot;us-west-2&amp;quot;,
        &amp;quot;route_53_zone_id&amp;quot;: &amp;quot;MY_ZONE_ID&amp;quot;,
        &amp;quot;route_53_record_ttl&amp;quot;: 60,
        &amp;quot;route_53_record_type&amp;quot;: &amp;quot;A&amp;quot;,
        &amp;quot;shared_secret&amp;quot;: &amp;quot;SHARED_SECRET_1&amp;quot;
    },
    &amp;quot;host2.dyn.example.com.&amp;quot;: {
        &amp;quot;aws_region&amp;quot;: &amp;quot;us-west-2&amp;quot;,
        &amp;quot;route_53_zone_id&amp;quot;: &amp;quot;MY_ZONE_ID&amp;quot;,
        &amp;quot;route_53_record_ttl&amp;quot;: 60,
        &amp;quot;route_53_record_type&amp;quot;: &amp;quot;A&amp;quot;,
        &amp;quot;shared_secret&amp;quot;: &amp;quot;SHARED_SECRET_2&amp;quot;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An example of a &lt;code&gt;bash&lt;/code&gt;-based client which can be set up as a &lt;code&gt;cron&lt;/code&gt; job is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;
#!/bin/bash

#./dynamic_dns_lambda_client.sh host1.dyn.example.com. SHARED_SECRET_1 &amp;quot;abc123.execute-api.us-west-2.amazonaws.com/prod&amp;quot;

if [ $# -eq 0 ]
    then
    echo &amp;quot;Usage: $0 host1.dyn.example.com. sharedsecret \&amp;quot;abc123.execute-api.us-west-2.amazonaws.com/prod\&amp;quot;&amp;quot;
    exit
fi

myHostname=$1
mySharedSecret=$2
myAPIURL=$3

myIP=`curl -q -s  &amp;quot;https://$myAPIURL?mode=get&amp;quot; | egrep -o &#39;[0-9\.]+&#39;`
myHash=`echo -n $myIP$myHostname$mySharedSecret | shasum -a 256 | awk &#39;{print $1}&#39;`

curl -q -s &amp;quot;https://$myAPIURL?mode=set&amp;amp;hostname=$myHostname&amp;amp;hash=$myHash&amp;quot;
echo

&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>AWS Security Groups and Dynamic IP Addresses</title>
      <link>https://joshua_hull.gitlab.io/post/2016-06-10-aws-security-groups-dynamic-ips/</link>
      <pubDate>Fri, 10 Jun 2016 09:48:12 -0400</pubDate>
      <guid>https://joshua_hull.gitlab.io/post/2016-06-10-aws-security-groups-dynamic-ips/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You are given the task to only allow access to certain AWS resources to the office you work in. You create a Security Group and ask a colleague for the external IP address range assigned to the office. He tells you that there is not static range. The office, along with the rest of the building, share a commercial ISP with dynamic addresses. In addition to that, there is not one but three IPSs that are load balanced for outgoing traffic. The external IP address of the machine you&#39;re working on can theoretically change on a per request basis.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You decide there&#39;s nothing you can do about the rest of the building being able to access the resources. It isn&#39;t a security threat if they can and there no obvious way that they would be able to find the resources. You decide you&#39;ll write a Python script that gets your public IP address. It will then calculate if that address is in the list of address ranges you already know about. If the address is in the range then everything is good. If the address isn&#39;t in the range then the script will calculate a new range so you can go update the Security Group.&lt;/p&gt;
&lt;p&gt;After an hour or so you have the following script working:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;
import urllib.request as urllib2
import re
from netaddr import *
import pprint

# Get our external network address and calculate a Class C CIDR based on it.
resource = urllib2.urlopen(&#39;http://ipinfo.io/ip&#39;)
ext_ip =  resource.read().decode(resource.headers.get_content_charset())
my_ip = re.match(r&amp;quot;^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$&amp;quot;,ext_ip).group()
my_cidr = IPNetwork(my_ip)
my_cidr.prefixlen = 24
my_cidr = my_cidr.cidr

print(&amp;quot;IP Address: &amp;quot; + str(my_ip))
print(&amp;quot;CIDR: &amp;quot; + str(my_cidr))


# The list of existing CIDR ranges
lines = []
ip_list = []
with open(&#39;Desktop/AWS/IPs.txt&#39;) as infile:
    for line in infile:
        ip_list.append(IPNetwork(line))
        lines.append(line)

# Find the largest ranges that cover our CIDR ranges.
ip_merged = cidr_merge(ip_list)

# Try to find if we are in an existing CIDR by looking at the first address in
# the range.
in_range = False
for network in ip_merged:
    if my_cidr[0] == network[0]:
        in_range = True
        print(&amp;quot;Possible match: &amp;quot; + str(network))
        pass
    pass

if in_range:
    print(&amp;quot;We should be in the existing range:&amp;quot;)
    pass
else:
    print(&amp;quot;We might not be in the existing range. Proposed new range:&amp;quot;)
    ip_list.append(my_cidr)
    ip_merged = cidr_merge(ip_list)
    pass

lines = []
for network in ip_merged:
    lines.append(str(network))

with open(&#39;Desktop/AWS/IPs.txt&#39;, &#39;w&#39;) as outfile:
    for line in lines:
        outfile.write(line)

pprint.pprint(ip_merged)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whenever someone in your office complains that they can&#39;t access the resources they need you simple have them run this script. If it states that the existing range should match then you have your colleague run the script several times spaced several minutes apart. This should allow the script to catch the new IP address the ISP is using.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extra Credit&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You decide that having to have your colleagues run this script whenever their work is interrupted is not sufficient. You want to have the script run on a schedule and update the Security Group on it&#39;s own. You append the following to the python script to facilitate the automatic updates:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;ip_replace_contents = &#39;&#39;

for network in ip_merged:
  ip_replace_contents = ip_replace_contents + &amp;quot;\&amp;quot;&amp;quot; + str(network) &amp;quot;\&amp;quot;, &amp;quot;
  pass
ip_replace_contents = ip_replace_contents.rstrip(&amp;quot;, &amp;quot;)

replacements = {&#39;IP_REPLACE_CONTENTS&#39;:ip_replace_contents}

with open(&#39;Desktop/Terraform/Security_Groups.tf&#39;) as infile, open(&#39;Desktop/Terraform/&#39; + datetime.datetime.now().strftime(&amp;quot;%Y-%m-%d&amp;quot;) + &#39;/Security_Groups.tf&#39;, &#39;w&#39;) as outfile:
    for line in infile:
        for src, target in replacements.iteritems():
            line = line.replace(src, target)
        outfile.write(line)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You schedule the following shell script to run every week day at 6:00 AM to update the Security Group before the work day begins:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/bash

NOW=$(date +&amp;quot;%Y-%m-%d&amp;quot;)

python dynamic_ips.py
terraform apply Desktop/Terraform/$NOW

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everyone in the office is happy knowing that the resources on AWS are safe and they don&#39;t have to worry about not being able to access them themselves.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Hello, World</title>
      <link>https://joshua_hull.gitlab.io/post/2016-06-08-first-post/</link>
      <pubDate>Wed, 08 Jun 2016 14:23:02 -0400</pubDate>
      <guid>https://joshua_hull.gitlab.io/post/2016-06-08-first-post/</guid>
      <description>&lt;p&gt;Hello, World&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
